#include "config.h"

#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <openssl/md5.h>
#include <openssl/aes.h>
#include <dirent.h>

#define DBG_SUBSYS S_LIBCLUSTER

#include "rpc_proto.h"
#include "ynet_rpc.h"
#include "net_global.h"
#include "fnotify.h"
#include "sdevent.h"
#include "timer.h"
#include "core.h"
#include "fence.h"
#include "mem_cache.h"
#include "cluster.h"
#include "node.h"
#include "bh.h"
#include "main_loop.h"
#include "variable.h"
#include "net_global.h"
#include "net_vip.h"
#include "configure.h"
#include "lichconf.h"
#include "longtask.h"
#include "sequence.h"
#include "dbg.h"
#include "get_version.h"
#include "rdma_event.h"
#include "lich_qos.h"

#define LICH_META_VERSION_CRANBERRY    "CRANBERRY (4.0.0)\n"

/*If more than Max_reboot number of restarts occur in the last Max_date seconds,*/
/*then the supervisor terminates all the child processes and then itself.*/
#define MAX_REBOOT 2
#define MAX_DATE 600
#define _MAX_REBOOT (MAX_REBOOT + 1000)

extern analysis_t *default_analysis;
extern int nofile_max;
int *exit_times;
int exit_count;

inline static void static_config_check()
{

#if !ENABLE_HUGE_MALLOC
        DERROR("ENABLE_HUGE_MALLOC disabled\n");
#endif

#if ENABLE_SCHEDULE_SELF_DEBUG
        DERROR("ENABLE_SCHEDULE_SELF_DEBUG enabled\n");
#endif

#if ENABLE_CHUNK_DEBUG
        DERROR("ENABLE_CHUNK_DEBUG enabled\n");
#endif
}

inline static int __set_environment() 
{
        int fd, ret;
        char len[MAX_BUF_LEN];

        memset(len, 0x0, MAX_BUF_LEN);

        sprintf(len, "%d", SO_XMITBUF);

        fd = open("/proc/sys/net/core/wmem_max", O_RDWR | O_TRUNC);
        if (fd < 0) {
                ret = errno;
                GOTO(err_ret, ret);
        }

        ret = _write(fd, len, strlen(len));
        if ((unsigned)ret != strlen(len)) {
                ret = -ret;
                GOTO(err_ret, ret);
        }

        sy_close(fd);

        fd = open("/proc/sys/net/core/rmem_max", O_RDWR | O_TRUNC);
        if (fd < 0) {
                ret = errno;
                GOTO(err_ret, ret);
        }

        ret = _write(fd, len, strlen(len));
        if ((unsigned)ret != strlen(len)) {
                ret = -ret;
                GOTO(err_ret, ret);
        }

        sy_close(fd);

        return 0;
err_ret:
        return ret;
}

static int __check_environment()
{
        int ret, value;
        char buf[MAX_BUF_LEN];

        ret = _get_value("/proc/sys/net/core/wmem_max", buf, MAX_BUF_LEN);
        if (ret < 0) {
                ret = errno;
                GOTO(err_ret, ret);
        }

        value = atoi(buf);
        if (value < SO_XMITBUF) {
                DINFO("please set net.core.wmem_max %u, current %s\n", SO_XMITBUF, buf);
        }

        ret = _get_value("/proc/sys/net/core/rmem_max", buf, MAX_BUF_LEN);
        if (ret < 0) {
                ret = errno;
                GOTO(err_ret, ret);
        }

        value = atoi(buf);
        if (value < SO_XMITBUF) {
                DINFO("please set net.core.rmem_max %u, current %s\n", SO_XMITBUF, buf);
        }

        return 0;
err_ret:
        return ret;
}

static int env_version_check(const char *home)
{
        int ret, fd = -1;
        char path[MAX_PATH_LEN], buf[MAX_BUF_LEN];
        struct stat stbuf;

        /*old version*/
        sprintf(path, "%s/version", home);

        ret = stat(path, &stbuf);
        if (ret == 0) {
                FATAL_EXIT("old version found");
        }

        sprintf(path, "%s/%s/version", home, LICH_STATUS_PRE);

        ret = path_validate(path, 0, 1);
        if (unlikely(ret))
                GOTO(err_ret, ret);

        ret = stat(path, &stbuf);
        if (unlikely(ret)) {
                ret = errno;

                if (ret == ENOENT) {
                        fd = open(path, O_CREAT | O_EXCL | O_RDWR, 0644);
                        if (fd < 0) {
                                ret = -fd;
                                GOTO(err_ret, ret);
                        }

                        ret = _pwrite(fd, LICH_META_VERSION_CRANBERRY, strlen(LICH_META_VERSION_CRANBERRY), 0);
                        if (ret < 0) {
                                ret = -ret;
                                GOTO(err_ret, ret);
                        }

                        goto out;
                } else {
                        DERROR("path %s\n", path);

                        GOTO(err_ret, ret);
                }
        }

        fd = open(path, O_RDWR);
        if (fd < 0) {
                ret = -fd;
                GOTO(err_ret, ret);
        }

        memset(buf, 0x0, MAX_BUF_LEN);
        ret = read(fd, buf, MAX_BUF_LEN);
        if (ret < 0) {
                ret = errno;
                GOTO(err_ret, ret);
        }

        DINFO("data version %s", buf);
        DINFO("lich version %s\n", LVERSION);

        if (strcmp(buf, LICH_META_VERSION_CRANBERRY)) {
                DERROR("got data version: %s", buf);
                DERROR("supported version: %s", LICH_META_VERSION_CRANBERRY);
                DERROR("unsupported format, exit\n");
                EXIT(EINVAL);
        }

out:
        close(fd);

        return 0;
err_ret:
        close(fd);
        return ret;
}

static void __ng_init()
{
        YASSERT(ng.inited == 0);

        memset(&ng, 0x0, sizeof(net_global_t));

        ng.master.u.nid.id = 0;
        ng.local_nid.id = 0;
        ng.master_magic = -1;
        ng.offline = 0;
        ng.port = YNET_PORT_NULL;
        ng.inited = 1;
}

static void __exit_handler_init()
{
        int ret;
        ret = ymalloc((void**)&exit_times, _MAX_REBOOT * sizeof(int));
        if (unlikely(ret)) {
                fprintf(stderr, "exit_handler_init error: %d\n", ret);
                EXIT(ret);
        }

        memset(exit_times, 0x0, sizeof(*exit_times));
        exit_count = 0;
}

static void  __exit_handler(int *over)
{
        int index, max, min, tmp, i, diff, total;

        index = exit_count % _MAX_REBOOT;
        exit_times[index] = gettime();
        /*printf("exit_count: %d\n", exit_count);*/

        max = exit_times[0];
        min = exit_times[0];
        total = 0;
        for (i = 0; i < _MAX_REBOOT; i++) {
                if (exit_times[i]) {
                        tmp = exit_times[i];
                        if (max < tmp) {
                                max = tmp;
                        }

                        if (min > tmp) {
                                min = tmp;
                        }

                        total++;
                }
        }

        diff = max - min;
        if ((total >= _MAX_REBOOT) && (max - min < MAX_DATE)) {
                DERROR("sorry, it's over, count: %d, diff: %d\n", exit_count, diff);
                *over = 1;
        } else {
                DWARN("hi, just retry, conut: %d, diff: %d\n", exit_count, diff);
                *over = 0;
        }

        exit_count++;
}

int env_init(const char *home)
{
        int ret;
        char path[MAX_PATH_LEN];

        snprintf(path, MAX_PATH_LEN, "%s/status/status", home);

        static_config_check();
        
        ret = sequence_init(home);
        if (unlikely(ret))
                GOTO(err_ret, ret);

        ret = daemon_update(path, "starting\n");
        if (unlikely(ret))
                GOTO(err_ret, ret);

        ret = mem_cache_init();
        if (unlikely(ret))
                GOTO(err_ret, ret);

        ret = etcd_init();
        if (unlikely(ret))
                GOTO(err_ret, ret);

        // async events
        ret = worker_init();
        if (unlikely(ret))
                GOTO(err_ret, ret);

        strcpy(ng.home, home);

        __check_environment();
        ng.xmitbuf = SO_XMITBUF;

        ret = timer_init(0);
        if (unlikely(ret))
                GOTO(err_ret, ret);

        analysis_init();

        // TODO useless?
        ret = bh_init();
        if (unlikely(ret))
                GOTO(err_ret, ret);

        ng.daemon = 1;

        ret = fnotify_init();
        if (unlikely(ret))
                GOTO(err_ret, ret);

        ret = jobdock_worker_create(&jobtracker, "default");
        if (unlikely(ret))
                GOTO(err_ret, ret);

        ret = sdevent_init(nofile_max);
        if (unlikely(ret))
                GOTO(err_ret, ret);

        ret = rdma_event_init();
        if (unlikely(ret))
                GOTO(err_ret, ret);

        ret = schedule_init();
        if (unlikely(ret))
                GOTO(err_ret, ret);

        ret = variable_init();
        if (unlikely(ret)) {
                UNIMPLEMENTED(__DUMP__);
        }

        ret = global_mem_init(1);
        if (ret)
                GOTO(err_ret, ret);

        ret = fastrandom_init();
        if (unlikely(ret)) {
                UNIMPLEMENTED(__DUMP__);
        }

        /**
         * 处理rpc
         */
        ret = main_loop_create(gloconf.main_loop_threads);
        if (unlikely(ret))
                GOTO(err_ret, ret);

        ret = core_init(CORE_FLAG_PASSIVE | CORE_FLAG_AIO);
        if (unlikely(ret))
                GOTO(err_ret, ret);

        ret = rpc_init(1);
        if (unlikely(ret))
                GOTO(err_ret, ret);

        ret = maping_init();
        if (unlikely(ret))
                GOTO(err_ret, ret);

        ret = fence_init(ng.home);
        if (unlikely(ret))
                GOTO(err_ret, ret);

        // TODO belows depends on fnotify module
        ret = dmsg_init();
        if (unlikely(ret))
                GOTO(err_ret, ret);

        ret = qos_config_init();
        if (unlikely(ret))
                GOTO(err_ret, ret);

        ret = network_init();
        if (unlikely(ret))
                GOTO(err_ret, ret);

        if (sanconf.iscsi_vip.vip_count) {
                ret = netvip_init();
                if (unlikely(ret))
                        GOTO(err_ret, ret);
        }

        if (gloconf.performance_analysis) {
                ret = analysis_create(&default_analysis, "default");
                if (unlikely(ret))
                        GOTO(err_ret, ret);
        }

        ng.uptime = gettime();
        ng.earliest_uptime = gettime();

        return 0;
err_ret:
        return ret;
}

int env_prep(const char *home, int daemon)
{
        int i, ret;
        char path[YLOG_TYPE_MAX][MAX_PATH_LEN];
        int fds[YLOG_TYPE_MAX];

        //strcpy(gloconf.cluster_name, sanconf.iqn);

        DBUG("daemon %u home %s\n", daemon, home);

        ret = conf_init();
        if (unlikely(ret))
                GOTO(err_ret, ret);

        dbg_sub_init();

        __ng_init();
        
        if (daemon) {
                snprintf(path[YLOG_TYPE_STD], MAX_PATH_LEN, "%s/log/lich.log", gloconf.home);
                snprintf(path[YLOG_TYPE_PERF], MAX_PATH_LEN, "%s/log/perf.log", gloconf.home);
                snprintf(path[YLOG_TYPE_BALANCE], MAX_PATH_LEN, "%s/log/balance.log", gloconf.home);
                snprintf(path[YLOG_TYPE_RAMDISK], MAX_PATH_LEN, "%s/log/ramdisk.log", gloconf.home);

                ret = path_validate(path[YLOG_TYPE_STD], 0, 1);
                if (unlikely(ret))
                        GOTO(err_ret, ret);

                ret = path_validate(path[YLOG_TYPE_PERF], 0, 1);
                if (unlikely(ret))
                        GOTO(err_ret, ret);

                ret = path_validate(path[YLOG_TYPE_BALANCE], 0, 1);
                if (unlikely(ret))
                        GOTO(err_ret, ret);

                ret = path_validate(path[YLOG_TYPE_RAMDISK], 0, 1);
                if (unlikely(ret))
                        GOTO(err_ret, ret);

                (void) ylog_init(YLOG_FILE, path);

                for(i = 0; i < YLOG_TYPE_MAX; i++){
                        fds[i] = (*g_ylogs)[i].logfd;
                }

                ret =  daemonlize(1, gloconf.maxcore, NULL, fds, YLOG_TYPE_MAX);
                if (unlikely(ret)){
                        GOTO(err_ret, ret);
                }

                syslog_init();
        } else {
                (void) ylog_init(YLOG_STDERR, NULL);

                ret = daemonlize(0, gloconf.maxcore, NULL, NULL, 0);
                if (unlikely(ret))
                        GOTO(err_ret, ret);

                syslog_init();
        }

        return 0;
err_ret:
        return ret;
}

int env_server_run(int daemon, int (*server)(void *), void *args)
{
        int ret, son, status, fd, over, network_flag = 1, retry = 0;
        char path[MAX_PATH_LEN], *home;
        int fork_count = 0;

        home = args;
        ret = env_version_check(home);
        if (unlikely(ret))
                GOTO(err_ret, ret);

        snprintf(path, MAX_PATH_LEN, "%s/status/status", home);

        fd = daemon_lock(path);
        if (fd < 0) {
                ret = -fd;
                GOTO(err_ret, ret);
        }

        __exit_handler_init();

        while (srv_running && daemon) {
                son = fork();
                fork_count++;
                DFATAL("son %d fork_count %d\n", son, fork_count);

                switch (son) {
                case -1:
                        ret = errno;
                        GOTO(err_ret, ret);
                case 0:
                        DINFO("\n%s\n", YVERSION);
                        DINFO("son process started, son %d\n", getpid());
                        ret = server(args);
                        if (unlikely(ret)) {
                                DERROR("service start fail, exit\n");
                                return ret;
                        }
                        DINFO("son process stopped\n");

                        goto out;
                        break;
                default:
                        DINFO("parent process started, parent %d son %d\n", getpid(), son);
                        while (srv_running) {
                                ret = wait(&status);
                                DINFO("parent process wait son exit ret:%d son:%d status:%d\n", ret, son, status);
                                if (ret == son)
                                        break;

                                ret = errno;
                                DERROR("Monitor: %d\n", ret);
                        }
                        DINFO("parent process stopped\n");

                        if (WIFEXITED(status)) {
                                ret = WEXITSTATUS(status);

                                if (ret == 0) {
                                        DINFO("worker exit normally\n");
                                        SINFO(0, "%s, worker exit normally\n", M_FUSIONSTOR_EXIT_NORMAL);
                                        goto out;
                                } else if (ret == EAGAIN) {
                                        DINFO("worker require restart, restarting...\n");
                                        SINFO(1, "%s, worker require restart, restarting...\n", M_FUSIONSTOR_RESTART);
                                        continue;
                                } else if (ret == EADDRINUSE) {
                                        if (retry < 10) {
                                                retry++;
                                                DWARN(" Address already in use, retry...\n");
                                                sleep(10);
                                                continue;
                                        } else {
                                                DERROR("Monitor: worker exited %d, ret %u\n",
                                                       WEXITSTATUS(status), ret);
                                                SERROR(2, "%s, Monitor: worker exited %d, ret %u\n", M_FUSIONSTOR_EXIT_ERROR,
                                                       WEXITSTATUS(status), ret);
                                                EXIT(ret);
                                        }
                                } else if (ret == ENONET) {
                                        DWARN("network down, retry later...\n");
                                        while (1) {
                                                sleep(10);
                                                ret = fence_test_sync();
                                                if (unlikely(ret)) {
                                                        DWARN("network down, retry fail...\n");
                                                        continue;
                                                }

                                                break;
                                        }

                                        DWARN("network recovered, continue...\n");
                                        SINFO(3, "%s, network recovered, continue...\n", M_FUSIONSTOR_RESTART);
                                        continue;
                                } else {
                                        DERROR("Monitor: worker exited %d, ret %u\n",
                                               WEXITSTATUS(status), ret);
                                        SERROR(4, "%s, Monitor: worker exited %d, ret %u\n", M_FUSIONSTOR_EXIT_ERROR,
                                               WEXITSTATUS(status), ret);
                                        EXIT(ret);
                                }

                                break;
                        } else if (WIFSIGNALED(status)) {
                                DERROR("Monitor: worker exited on signal %d, runing %u\n",
                                       WTERMSIG(status), srv_running);

                                if (gloconf.restart) {
                                        while (1) {
                                                ret = fence_test_sync();
                                                if (unlikely(ret)) {
                                                        DWARN("network down, retry later...\n");
                                                        network_flag = 0;
                                                        sleep(10);
                                                        continue;
                                                }

                                                if (!network_flag) {
                                                        network_flag = 1;
                                                        DWARN("network recovered, continue...\n");
                                                }

                                                break;
                                        }
                                        __exit_handler(&over);
                                        if (over) {
                                                DERROR("einval\n");
                                                SERROR(6, "%s, einval\n", M_FUSIONSTOR_EXIT_ERROR);
                                                EXIT(EINVAL);
                                        }

                                        DINFO("worker require restart, restarting...\n");
                                        SINFO(5, "%s, worker require restart, restarting...\n", M_FUSIONSTOR_RESTART);
                                        continue;
                                }

                                DERROR("einval\n");
                                SERROR(7, "%s, einval\n", M_FUSIONSTOR_EXIT_ERROR);
                                EXIT(EINVAL);

                        } else {
                                DERROR("Monitor: worker exited (stopped?) %d\n", status);
                                SERROR(8, "%s, Monitor: worker exited (stopped?) %d\n", M_FUSIONSTOR_EXIT_ERROR, status);
                                EXIT(EINVAL);
                                //continue;
                        }
                }

                if (daemon == 0)
                        break;
        }

        if (!daemon) {
                ret = server(args);
                if (unlikely(ret))
                        GOTO(err_ret, ret);
        }

out:
        return 0;
err_ret:
        return ret;
}

int env_update_status(const char *status, int step)
{
        int ret;
        char path[MAX_PATH_LEN], buf[MAX_BUF_LEN];

        snprintf(path, MAX_PATH_LEN, "%s/status/status", ng.home);
        if (step == -1)
                snprintf(buf, MAX_PATH_LEN, "%s\n", status);
        else
                snprintf(buf, MAX_PATH_LEN, "%s:%d\n", status, step);

        DINFO("set %s value %s", path, buf);

        ret = daemon_update(path, buf);
        if (unlikely(ret))
                GOTO(err_ret, ret);

        return 0;
err_ret:
        return ret;
}

int env_init_common(int daemon)
{
        int ret;

        __ng_init();

        ng.xmitbuf = SO_XMITBUF;

        ret = variable_init();
        if (unlikely(ret)) {
                UNIMPLEMENTED(__DUMP__);
        }

        ret = fastrandom_init();
        if (unlikely(ret)) {
                UNIMPLEMENTED(__DUMP__);
        }
        
        ret = mem_cache_init();
        if (unlikely(ret))
                GOTO(err_ret, ret);

        ret = worker_init();
        if (unlikely(ret))
                GOTO(err_ret, ret);

        ret = bh_init();
        if (unlikely(ret))
                GOTO(err_ret, ret);

        ret = timer_init(0);
        if (unlikely(ret))
                GOTO(err_ret, ret);

        ret = jobdock_worker_create(&jobtracker, "default");
        if (unlikely(ret))
                GOTO(err_ret, ret);

        ret = sdevent_init(nofile_max);
        if (unlikely(ret))
                GOTO(err_ret, ret);

        ret = schedule_init();
        if (unlikely(ret))
                GOTO(err_ret, ret);

        ret = global_mem_init(0);
        if (ret)
                GOTO(err_ret, ret);

        ret = main_loop_create(1);
        if (unlikely(ret))
                GOTO(err_ret, ret);

        main_loop_start();

        ret = rpc_init(daemon);
        if (unlikely(ret))
                GOTO(err_ret, ret);

        ret = maping_init();
        if (unlikely(ret))
               GOTO(err_ret, ret);

        ret = fnotify_init();
        if (unlikely(ret))
                GOTO(err_ret, ret);

        ret = dmsg_init();
        if (unlikely(ret))
                GOTO(err_ret, ret);

        ret = network_init();
        if (unlikely(ret))
                GOTO(err_ret, ret);

        if (gloconf.performance_analysis) {
                ret = analysis_create(&default_analysis, "default");
                if (unlikely(ret))
                        GOTO(err_ret, ret);
        }

/*
        ret = fence_init(ng.home);
        if (unlikely(ret))
                GOTO(err_ret, ret);
*/

        if (sanconf.iscsi_gateway) {
                ret = node_get_nid(gloconf.home);
                if (unlikely(ret))
                        GOTO(err_ret, ret);
        }

        ng.daemon = 0;
        ng.uptime = gettime();
        ng.earliest_uptime = gettime();

        return 0;
err_ret:
        return ret;
}

int env_init_simple(const char *name)
{
        int ret;

        (void) name;

        /*
         * there is a risk, see the malloc implement in glibc
         */
        ret = conf_init();
        if (unlikely(ret))
                GOTO(err_ret, ret);

        dbg_sub_init();

        (void) ylog_init(YLOG_STDERR, NULL);
        (void) daemonlize(0, gloconf.maxcore, NULL, NULL, 0);
        
        ret = env_init_common(0);
        if (unlikely(ret))
                GOTO(err_ret, ret);

        return 0;
err_ret:
        return ret;
}
